# 5.3 PyTorch修改模型 除了自己构建PyTorch模型外,还有另一种应用场景:我们已经有一个现成的模型,但该模型中的部分结构不符合我们的要求,为了使用模型,我们需要对模型结构进行必要的修改。随着深度学习的发展和PyTorch越来越广泛的使用,有越来越多的开源模型可以供我们使用,很多时候我们也不必从头开始构建模型。因此,掌握如何修改PyTorch模型就显得尤为重要。 本节我们就来探索这一问题。经过本节的学习,你将收获: - 如何在已有模型的基础上: - 修改模型若干层 - 添加额外输入 - 添加额外输出 ## 5.3.1 修改模型层 我们这里以PyTorch官方视觉库torchvision预定义好的模型ResNet50为例,探索如何修改模型的某一层或者某几层。我们先看看模型的定义是怎样的: ```python # 导入必要的package import torch import torch.nn as nn from collections import OrderedDict import torchvision.models as models net = models.resnet50() print(net) ``` ``` ResNet( (conv1): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False) (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (relu): ReLU(inplace=True) (maxpool): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False) (layer1): Sequential( (0): Bottleneck( (conv1): Conv2d(64, 64, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (conv3): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn3): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (relu): ReLU(inplace=True) (downsample): Sequential( (0): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False) (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) ) ) .............. (avgpool): AdaptiveAvgPool2d(output_size=(1, 1)) (fc): Linear(in_features=2048, out_features=1000, bias=True) ) ``` 这里模型结构是为了适配ImageNet预训练的权重,因此最后全连接层(fc)的输出节点数是1000。 假设我们要用这个resnet模型去做一个10分类的问题,就应该修改模型的fc层,将其输出节点数替换为10。另外,我们觉得一层全连接层可能太少了,想再加一层。可以做如下修改: ```python classifier = nn.Sequential(OrderedDict([('fc1', nn.Linear(2048, 128)), ('relu1', nn.ReLU()), ('dropout1',nn.Dropout(0.5)), ('fc2', nn.Linear(128, 10)), ('output', nn.Softmax(dim=1)) ])) net.fc = classifier ``` 这里的操作相当于将模型(net)最后名称为“fc”的层替换成了名称为“classifier”的结构,该结构是我们自己定义的。这里使用了第一节介绍的Sequential+OrderedDict的模型定义方式。至此,我们就完成了模型的修改,现在的模型就可以去做10分类任务了。 ## 5.3.2 添加外部输入 有时候在模型训练中,除了已有模型的输入之外,还需要输入额外的信息。比如在CNN网络中,我们除了输入图像,还需要同时输入图像对应的其他信息,这时候就需要在已有的CNN网络中添加额外的输入变量。基本思路是:将原模型添加输入位置前的部分作为一个整体,同时在forward中定义好原模型不变的部分、添加的输入和后续层之间的连接关系,从而完成模型的修改。 我们以torchvision的resnet50模型为基础,任务还是10分类任务。不同点在于,我们希望利用已有的模型结构,在倒数第二层增加一个额外的输入变量add_variable来辅助预测。具体实现如下: ```python class Model(nn.Module): def __init__(self, net): super(Model, self).__init__() self.net = net self.relu = nn.ReLU() self.dropout = nn.Dropout(0.5) self.fc_add = nn.Linear(1001, 10, bias=True) self.output = nn.Softmax(dim=1) def forward(self, x, add_variable): x = self.net(x) x = torch.cat((self.dropout(self.relu(x)), add_variable.unsqueeze(1)),1) x = self.fc_add(x) x = self.output(x) return x ``` 这里的实现要点是通过torch.cat实现了tensor的拼接。torchvision中的resnet50输出是一个1000维的tensor,我们通过修改forward函数(配套定义一些层),先将1000维的tensor通过激活函数层和dropout层,再和外部输入变量"add_variable"拼接,最后通过全连接层映射到指定的输出维度10。 另外这里对外部输入变量"add_variable"进行unsqueeze操作是为了和net输出的tensor保持维度一致,常用于add_variable是单一数值 (scalar) 的情况,此时add_variable的维度是 (batch_size, ),需要在第二维补充维数1,从而可以和tensor进行torch.cat操作。对于unsqueeze操作可以复习下2.1节的内容和配套代码。 之后对我们修改好的模型结构进行实例化,就可以使用了: ```python net = models.resnet50() model = Model(net).cuda() ``` 另外别忘了,训练中在输入数据的时候要给两个inputs: ```python outputs = model(inputs, add_var) ``` ## 5.3.3 添加额外输出 有时候在模型训练中,除了模型最后的输出外,我们需要输出模型某一中间层的结果,以施加额外的监督,获得更好的中间层结果。基本的思路是修改模型定义中forward函数的return变量。 我们依然以resnet50做10分类任务为例,在已经定义好的模型结构上,同时输出1000维的倒数第二层和10维的最后一层结果。具体实现如下: ```python class Model(nn.Module): def __init__(self, net): super(Model, self).__init__() self.net = net self.relu = nn.ReLU() self.dropout = nn.Dropout(0.5) self.fc1 = nn.Linear(1000, 10, bias=True) self.output = nn.Softmax(dim=1) def forward(self, x, add_variable): x1000 = self.net(x) x10 = self.dropout(self.relu(x1000)) x10 = self.fc1(x10) x10 = self.output(x10) return x10, x1000 ``` 之后对我们修改好的模型结构进行实例化,就可以使用了: ```python import torchvision.models as models net = models.resnet50() model = Model(net).cuda() ``` 另外别忘了,训练中在输入数据后会有两个outputs: ```python out10, out1000 = model(inputs, add_var) ```